﻿using JobBatDeal.Model;
using log4net.Repository.Hierarchy;
using Nest;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WindowsFormsApplication1.Common;

namespace JobBatDeal.Common.EsTool
{
    public class FanJiaHotelEs
    {
        /// <summary>
        /// 算分缩放比例系数
        /// </summary>
        private const double ScoreRatio = 2D;

        public string GetIndexName()
        {
            var indexName = ConfigurationManager.AppSettings["EsDefaultIndex"].ToLower();
            return indexName;
        }

        public async Task CreateIndexAsync()
        {
            var indexName = GetIndexName();

            //索引不存在会创建索引。索引存在，报错，忽略报错
            var createIndexDescriptor = new CreateIndexDescriptor(indexName);

            createIndexDescriptor.Mappings(selector => selector.Map<FanJiaHotel>(
                r => r.Properties(
                    ps => ps
                        .GeoPoint(gp => gp.Name(pn => pn.HotelGeo))
                        .Text(s => s.Name(e => e.Name).Analyzer("ik_smart").SearchAnalyzer("ik_smart"))
                        .Text(s => s.Name(e => e.Address).Analyzer("ik_smart").SearchAnalyzer("ik_smart"))
                ).AutoMap()
            ));

            var createIndexResponse = await ElasticSearchHelper.GetClient().CreateIndexAsync(createIndexDescriptor);
        }

        /// <summary>
        /// 30分钟更新一次
        /// </summary>
        /// <param name="cityId"></param>
        /// <returns></returns>
        public async Task GenerateByCityAsync(Guid? cityId)
        {
            var isNeedBulk = await IsNeedBulkAsync(cityId);
            if (isNeedBulk)
            {
                //var infos = await _hotelBasicInfoRepository.GetHotelListByCityIdAsync(cityId);
                //if (infos == null || !infos.Any())
                //{
                //    return;
                //}
                //DeleteByCityId(cityId);
                //foreach (var fanJiaHotel in infos)
                //{
                //    fanJiaHotel.CreateDateTime = DateTime.Now;
                //}
                //ElasticSearchHelper.BulkAll(infos);
            }
        }

        /// <summary>
        /// 30分钟更新一次
        /// </summary>
        /// <param name="cityId"></param>
        /// <returns></returns>
        public async Task GenerateByCityAsync(Guid? cityId, List<FanJiaHotel> infos)
        {
            var isNeedBulk = await IsNeedBulkAsync(cityId);
            if (isNeedBulk)
            {
                if (infos == null || !infos.Any())
                {
                    return;
                }
                DeleteByCityId(cityId);
                foreach (var fanJiaHotel in infos)
                {
                    fanJiaHotel.CreateDateTime = DateTime.Now;
                }
                ElasticSearchHelper.BulkAll(infos);
            }
        }

        public async Task<bool> IsNeedBulkAsync(Guid? cityId, double minutes = 30D)
        {
            var searchResponse = await ElasticSearchHelper.GetClient().SearchAsync<FanJiaHotel>(s =>
                s.Query(
                    q => q.Term(selector => selector.Field(_ => _.CityId).Value(cityId))
                ).Sort(selector => selector.Descending(_ => _.CreateDateTime)).Size(1));

            if (!searchResponse.Documents.Any())
            {
                return true;
            }

            var fanJiaHotel = searchResponse.Documents.FirstOrDefault();
            if (fanJiaHotel == null)
            {
                return true;
            }

            if (fanJiaHotel.CreateDateTime < DateTime.Now.AddMinutes(-minutes))
            {
                return true;
            }

            return false;
        }

        public long DeleteByCityId(Guid? cityId)
        {
            var deleteResponse = ElasticSearchHelper.GetClient().DeleteByQuery<FanJiaHotel>(selector =>
                selector.Query(
                    q => q.Term(r => r.CityId, cityId)
                )
            );

            return deleteResponse.Deleted;
        }

        public async Task<double?> GetMaxScoreAsync(FanJiaHotel hotel, double distance = 1000D, DistanceUnit unit = DistanceUnit.Meters)
        {
            if (!hotel.Latitude.HasValue || !hotel.Longitude.HasValue)
            {
                return null;
            }

            if (hotel.Latitude > 90 || hotel.Latitude < -90)
            {
                return null;
            }

            var searchResponse = await ElasticSearchHelper.GetClient().SearchAsync<FanJiaHotel>(s => s.Query(query => query.Bool(
                    boolQuery => boolQuery.Should(shouldQuery => shouldQuery.Match(
                                shouldQueryMatch => shouldQueryMatch.Field(r => r.Address).Query(hotel.Address)
                            ), shouldQuery => shouldQuery.GeoDistance(g => g.Field(p => p.HotelGeo)
                                .Location(hotel.Latitude.Value, hotel.Longitude.Value)
                                .Distance(distance, unit)
                            ), shouldQuery => shouldQuery.Match(
                                shouldQueryMatch => shouldQueryMatch.Field(r => r.Phone).Query(hotel.Phone)
                            )
                        )
                        .Must(mustQuery => mustQuery
                                .Term(ts =>
                                    ts.Field(tsf => tsf.CityId)
                                        .Value(hotel.CityId)
                                ), mustQuery => mustQuery.Match(ms =>
                                ms.Field(f => f.Name)
                                    .Query(hotel.Name))
                        )
                )

            ).Size(1));

            var maxScore = searchResponse?.MaxScore;
            return maxScore;
        }

        public async Task<long?> GetTotalAsync(FanJiaHotel hotel, double maxScore, double distance = 1000D, DistanceUnit unit = DistanceUnit.Meters)
        {
            if (!hotel.Latitude.HasValue || !hotel.Longitude.HasValue)
            {
                return null;
            }

            var minScore = maxScore / ScoreRatio;

            var searchResponse = await ElasticSearchHelper.GetClient().SearchAsync<FanJiaHotel>(s =>
                s.Query(query => query.Bool(
                    boolQuery => boolQuery.Should(shouldQuery => shouldQuery.Match(
                                shouldQueryMatch => shouldQueryMatch.Field(r => r.Address).Query(hotel.Address)
                            ), shouldQuery => shouldQuery.GeoDistance(g => g.Field(p => p.HotelGeo)
                                .Location(hotel.Latitude.Value, hotel.Longitude.Value)
                                .Distance(distance, unit)
                            ), shouldQuery => shouldQuery.Match(
                                shouldQueryMatch => shouldQueryMatch.Field(r => r.Phone).Query(hotel.Phone)
                            )
                        )
                        .Must(mustQuery => mustQuery
                                .Term(ts =>
                                    ts.Field(tsf => tsf.CityId)
                                        .Value(hotel.CityId)
                                ), mustQuery => mustQuery.Match(ms =>
                                ms.Field(f => f.Name)
                                    .Query(hotel.Name))
                        )
                )

            ).MinScore(minScore).Size(1));

            var total = searchResponse?.Total;
            return total;
        }

        public async Task<List<FanJiaHotel>> SearchAsync(FanJiaHotel hotel, double maxScore, long total, double distance = 1000D, DistanceUnit unit = DistanceUnit.Meters)
        {
            try
            {
                if (!hotel.Latitude.HasValue || !hotel.Longitude.HasValue)
                {
                    return null;
                }

                var minScore = maxScore / ScoreRatio;

                var searchResponse = await ElasticSearchHelper.GetClient().SearchAsync<FanJiaHotel>(s =>
                    s.Query(query => query.Bool(
                            boolQuery => boolQuery.Should(shouldQuery => shouldQuery.Match(
                                        shouldQueryMatch => shouldQueryMatch.Field(r => r.Address).Query(hotel.Address)
                                    ), shouldQuery => shouldQuery.GeoDistance(g => g.Field(p => p.HotelGeo)
                                        .Location(hotel.Latitude.Value, hotel.Longitude.Value)
                                        .Distance(distance, unit)
                                    ), shouldQuery => shouldQuery.Match(
                                        shouldQueryMatch => shouldQueryMatch.Field(r => r.Phone).Query(hotel.Phone)
                                    )
                                )
                                .Must(mustQuery => mustQuery
                                        .Term(ts =>
                                            ts.Field(tsf => tsf.CityId)
                                                .Value(hotel.CityId)
                                        ), mustQuery => mustQuery.Match(ms =>
                                        ms.Field(f => f.Name)
                                            .Query(hotel.Name))
                                )
                        )

                    ).MinScore(minScore).Size((int)total));

                var hotelList = searchResponse.Hits.Select(_ => _.Source).ToList();
                return hotelList;
            }
            catch (Exception ex)
            {
                LogHelper.logError.Error(ex.Message, ex);
                return null;
            }
        }

        public async Task<List<Guid>> SearchHotelGeoAsync(double lat, double lon, double distance, DistanceUnit unit, int? size)
        {
            try
            {
                var searchResponse = await ElasticSearchHelper.GetClient().SearchAsync<HotelGeo>(s =>
                    s.Query(q =>
                        q.GeoDistance(g =>
                            g.Field(p => p.GeoLocation)
                                .DistanceType(GeoDistanceType.Arc)
                                .Location(lat, lon)
                                .Distance(distance, unit)
                                .ValidationMethod(GeoValidationMethod.IgnoreMalformed))).Size(size));

                if (searchResponse.Documents.Any())
                {
                    return searchResponse.Documents.Select(_ => _.HotelId).ToList();
                }
                else
                {
                    return new List<Guid>();
                }
            }
            catch (Exception e)
            {
                LogHelper.logError.Error(e.Message, e);
                return new List<Guid>();
            }
        }
    }


    [ElasticsearchType(IdProperty = nameof(HotelId))]
    internal class HotelGeo
    {
        /// <summary>
        /// 城市 ID
        /// </summary>
        public Guid CityId { get; set; }

        public Guid HotelId { get; set; }

        public GeoLocation GeoLocation { get; set; }
    }
}
